﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace RapidTestMapMaker
{
    public class BigMap
    {
        public BigMap()
        {
            layers = new List<MapLayer>();
            layersRO = layers.AsReadOnly();
            activeLayer = new MapLayer();
            activeLayer.Visible = true;
            activeLayer.Name = "Base Layer";
            layers.Add(activeLayer);
            MapLayer walls = new MapLayer();
            walls.Name = "Walls";
            walls.IsWall = true;
            walls.Visible = true;
            layers.Add(walls);
        }

        public Size ImageSize
        { get { return ImageBounds.Size; } }

        public Rectangle ImageBounds
        { get { return GetImageBounds(true, true); } }

        public Rectangle ImageBoundsIgnoringWalls
        { get { return GetImageBounds(true, false); } }

        private Rectangle GetImageBounds(bool includeFloors, bool includeWalls)
        {
            int x = int.MaxValue;
            int y = int.MaxValue;
            int r = 0;
            int b = 0;
            foreach (MapLayer layer in layers)
            {
                if (layer.IsWall && !includeWalls) { continue; }
                if (!layer.IsWall && !includeFloors) { continue; }
                Rectangle layerBounds = layer.ImageBounds;
                if (layerBounds.Width == 0)
                { continue; }
                if (layerBounds.Right > r)
                { r = layerBounds.Right; }
                if (layerBounds.Bottom > b)
                { b = layerBounds.Bottom; }
                if (layerBounds.Left < x)
                { x = layerBounds.Left; }
                if (layerBounds.Top < y)
                { y = layerBounds.Top; }
            }
            return new Rectangle(x, y, r - x, b - y);
        }

        public IList<MapLayer> Layers { get { return layersRO; } }
        private List<MapLayer> layers;
        private IList<MapLayer> layersRO;

        private MapLayer activeLayer;
        public MapLayer ActiveLayer
        {
            get { return activeLayer; }
            set
            {
                if (layers.Contains(value))
                { activeLayer = value; }
                else
                { throw new ArgumentOutOfRangeException("specified layer not found in layers collection"); }
            }
        }

        public MapLayer this[int index]
        { get { return Layers[index]; } }

        public void AddLayer(MapLayer newLayer)
        {
            if (newLayer == null)
            { throw new ArgumentNullException(); }
            if (layers.Contains(newLayer))
            { throw new ArgumentException("this layer is already present"); }
            if (activeLayer == null)
            { activeLayer = newLayer; }
            layers.Add(newLayer);
        }

        public void ClearLayers()
        {
            layers.Clear();
            activeLayer = null;
        }
    }
    public class MapLayer
    {
        public bool Visible { get; set; }

        public Tile this[int x, int y]
        {
            get
            {
                LoadRegion(x, y);
                return currentRegionTiles[Common.plusMod(x, RegionWidth), Common.plusMod(y, RegionHeight)];
            }
            set
            {
                LoadRegion(x, y);
                currentRegionTiles[Common.plusMod(x, RegionWidth), Common.plusMod(y, RegionHeight)] = value;
                currentRegion.Dirty = true;
            }
        }



        public delegate void FetchTileDelegate(Tile tile, int x, int y);
        public void GetAllTilesInRectangle(Rectangle rect, FetchTileDelegate handler)
        { GetAllTilesInRectangle(rect.Left, rect.Top, rect.Right, rect.Bottom, handler); }
        public void GetAllTilesInRectangle(int left, int top, int right, int bottom, FetchTileDelegate handler)
        {
            SaveCurrentRegion();
            for (int x = left; x < right; x++)
            {
                for (int y = top; y < bottom; y++)
                {
                    handler(this[x, y], x, y);
                }
            }
        }

        private readonly int RegionWidth = 100;
        private readonly int RegionHeight = 100;

        private String currentRegionKey;
        private Dictionary<String, SmallMap> regions = new Dictionary<string, SmallMap>();
        SmallMap currentRegion;
        Tile[,] currentRegionTiles;

        private Rectangle imageBounds = Rectangle.Empty;

        private void LoadRegion(int x, int y)
        {
            Point regionOffset = CalcRegionOffset(x, y);
            String key = CalcMapKey(regionOffset.X, regionOffset.Y);
            if (currentRegionKey != key)
            {
                SaveCurrentRegion();
                if (!regions.TryGetValue(key, out currentRegion))
                {
                    currentRegion = new SmallMap()
                    {
                        xOffset = regionOffset.X,
                        yOffset = regionOffset.Y,
                        Tiles = new Tile[RegionWidth, RegionHeight]
                    };
                }
                currentRegionKey = key;
                currentRegionTiles = currentRegion.Tiles;
            }
        }

        private void SaveCurrentRegion()
        {
            if (currentRegionTiles == null || currentRegionKey == null || !currentRegion.Dirty)
            { return; }
            currentRegion.Dirty = false;
            bool found = false;
            int l = RegionWidth;
            int r = 0;
            int t = RegionHeight;
            int b = 0;
            for (int x = 0; x < RegionWidth; x++)
            {
                for (int y = 0; y < RegionHeight; y++)
                {
                    if (currentRegionTiles[x, y] != null)
                    {
                        found = true;
                        if (x < l) { l = x; }
                        if (x > r) { r = x; }
                        if (y < t) { t = y; }
                        if (y > b) { b = y; }
                    }
                }
            }
            if (found)
            {
                regions[currentRegionKey] = currentRegion;
                currentRegion.Left = l;
                currentRegion.Right = r;
                currentRegion.Top = t;
                currentRegion.Bottom = b;
            }
            else
            { regions.Remove(currentRegionKey); }
            CalcImageSize();
        }

        private Point CalcRegionOffset(int x, int y)
        {
            return new Point(x / RegionWidth, y / RegionHeight);
        }

        private String CalcMapKey(int x, int y)
        {
            return x + "." + y;
        }

        private void CalcImageSize()
        {
            int t = int.MaxValue;
            int l = int.MaxValue;
            int r = int.MinValue;
            int b = int.MinValue;
            foreach (SmallMap map in regions.Values)
            {
                if (map.xOffset * RegionWidth + map.Left < l)
                { l = map.xOffset * RegionWidth + map.Left; }
                if (map.xOffset * RegionWidth + map.Right > r)
                { r = map.xOffset * RegionWidth + map.Right; }
                if (map.yOffset * RegionHeight + map.Top < t)
                { t = map.yOffset * RegionHeight + map.Top; }
                if (map.yOffset * RegionHeight + map.Bottom > b)
                { b = map.yOffset * RegionHeight + map.Bottom; }
            }
            imageBounds = new Rectangle(l, t, r - l + 1, b - t + 1);
        }

        public Size ImageSize
        {
            get
            {
                SaveCurrentRegion();
                return imageBounds.Size;
            }
        }

        public Rectangle ImageBounds
        {
            get
            {
                SaveCurrentRegion();
                return imageBounds;
            }
        }

        private class SmallMap
        {
            public Tile[,] Tiles { get; set; }
            public int xOffset { get; set; }
            public int yOffset { get; set; }
            public int Top { get; set; }
            public int Left { get; set; }
            public int Right { get; set; }
            public int Bottom { get; set; }
            public bool Dirty { get; set; }
        }

        public bool IsWall { get; set; }
        public string Name { get; set; }
    }
}
